/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.core.json.converter.json.converter.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.je.bpm.core.json.converter.constants.EditorJsonConstants;
import com.je.bpm.core.json.converter.constants.StencilConstants;
import com.je.bpm.core.json.converter.json.converter.BpmnJsonConverterUtil;

import java.util.*;

public class JsonConverterUtil implements EditorJsonConstants, StencilConstants {

    public static String getPropertyValueAsString(String name, JsonNode objectNode) {
        String propertyValue = null;
        JsonNode propertyNode = getProperty(name, objectNode);
        if (propertyNode != null && "null".equalsIgnoreCase(propertyNode.asText()) == false) {
            propertyValue = propertyNode.asText();
        }
        return propertyValue;
    }

    public static boolean getPropertyValueAsBoolean(String name, JsonNode objectNode) {
        return getPropertyValueAsBoolean(name, objectNode, false);
    }

    public static boolean getPropertyValueAsBoolean(String name, JsonNode objectNode, boolean defaultValue) {
        boolean result = defaultValue;
        String stringValue = getPropertyValueAsString(name, objectNode);
        if (objectNode == null || stringValue == null) {
            return false;
        }
        if (PROPERTY_VALUE_YES.equalsIgnoreCase(stringValue) || "true".equalsIgnoreCase(stringValue)) {
            result = true;
        } else if (PROPERTY_VALUE_NO.equalsIgnoreCase(stringValue) || "false".equalsIgnoreCase(stringValue)) {
            result = false;
        } else if (stringValue.equals("1")) {
            return true;
        } else if (stringValue.equals("0")) {
            return false;
        }

        return result;
    }

    public static List<String> getPropertyValueAsList(String name, JsonNode objectNode) {
        List<String> resultList = new ArrayList();
        JsonNode propertyNode = getProperty(name, objectNode);
        if (propertyNode != null && !"null".equalsIgnoreCase(propertyNode.asText())) {
            String propertyValue = propertyNode.asText();
            String[] valueList = propertyValue.split(",");
            for (String value : valueList) {
                resultList.add(value.trim());
            }
        }
        return resultList;
    }

    public static JsonNode getProperty(String name, JsonNode objectNode) {
        JsonNode propertyNode = null;
        if (objectNode.get(EDITOR_SHAPE_PROPERTIES) != null) {
            JsonNode propertiesNode = objectNode.get(EDITOR_SHAPE_PROPERTIES);
            propertyNode = propertiesNode.get(name);
        }
        return propertyNode;
    }

    /**
     * Usable for BPMN 2.0 editor json: traverses all child shapes (also nested), goes into
     * the properties and sees if there is a matching property in the
     * 'properties' of the childshape and returns those in a list.
     * <p>
     * Returns a map with said json nodes, with the key the name of the childshape.
     */

    protected static List<JsonLookupResult> getBpmnProcessModelChildShapesPropertyValues(JsonNode editorJsonNode, String propertyName, List<String> allowedStencilTypes) {
        List<JsonLookupResult> result = new ArrayList<JsonLookupResult>();
        internalGetBpmnProcessChildShapePropertyValues(editorJsonNode, propertyName, allowedStencilTypes, result);
        return result;
    }

    protected static void internalGetBpmnProcessChildShapePropertyValues(JsonNode editorJsonNode, String propertyName, List<String> allowedStencilTypes, List<JsonLookupResult> result) {
        JsonNode childShapesNode = editorJsonNode.get("childShapes");
        if (childShapesNode != null && childShapesNode.isArray()) {
            ArrayNode childShapesArrayNode = (ArrayNode) childShapesNode;
            Iterator<JsonNode> childShapeNodeIterator = childShapesArrayNode.iterator();
            while (childShapeNodeIterator.hasNext()) {
                JsonNode childShapeNode = childShapeNodeIterator.next();

                String childShapeNodeStencilId = BpmnJsonConverterUtil.getStencilId(childShapeNode);
                boolean readPropertiesNode = allowedStencilTypes.contains(childShapeNodeStencilId);

                if (readPropertiesNode) {
                    // Properties
                    JsonNode properties = childShapeNode.get("properties");
                    if (properties != null && properties.has(propertyName)) {
                        JsonNode nameNode = properties.get("name");
                        JsonNode propertyNode = properties.get(propertyName);
                        result.add(new JsonLookupResult(BpmnJsonConverterUtil.getElementId(childShapeNode),
                                nameNode != null ? nameNode.asText() : null, propertyNode));
                    }
                }

                // Potential nested child shapes
                if (childShapeNode.has("childShapes")) {
                    internalGetBpmnProcessChildShapePropertyValues(childShapeNode, propertyName, allowedStencilTypes, result);
                }

            }
        }
    }

    public static List<JsonLookupResult> getBpmnProcessModelFormReferences(JsonNode editorJsonNode) {
        List<String> allowedStencilTypes = new ArrayList<String>();
        allowedStencilTypes.add(STENCIL_TASK_USER);
        allowedStencilTypes.add(STENCIL_EVENT_START_NONE);
        return getBpmnProcessModelChildShapesPropertyValues(editorJsonNode, "formreference", allowedStencilTypes);
    }

    public static List<JsonLookupResult> getBpmnProcessModelDecisionTableReferences(JsonNode editorJsonNode) {
        List<String> allowedStencilTypes = new ArrayList<String>();
        allowedStencilTypes.add(STENCIL_TASK_DECISION);
        return getBpmnProcessModelChildShapesPropertyValues(editorJsonNode, "decisiontaskdecisiontablereference", allowedStencilTypes);
    }

    // APP MODEL
    public static List<JsonNode> getAppModelReferencedProcessModels(JsonNode appModelJson) {
        List<JsonNode> result = new ArrayList<JsonNode>();
        if (appModelJson.has("models")) {
            ArrayNode modelsArrayNode = (ArrayNode) appModelJson.get("models");
            Iterator<JsonNode> modelArrayIterator = modelsArrayNode.iterator();
            while (modelArrayIterator.hasNext()) {
                result.add(modelArrayIterator.next());
            }
        }
        return result;
    }

    public static Set<String> getAppModelReferencedModelIds(JsonNode appModelJson) {
        if (appModelJson.has("models")) {
            return JsonConverterUtil.gatherStringPropertyFromJsonNodes(appModelJson.get("models"), "id");
        }
        return Collections.emptySet();
    }

    // GENERIC

    /**
     * Loops through a list of {@link JsonNode} instances, and stores the given property with given type in the returned list.
     * <p>
     * In Java 8, this probably could be done a lot cooler.
     */
    public static Set<Long> gatherLongPropertyFromJsonNodes(Iterable<JsonNode> jsonNodes, String propertyName) {
        Set<Long> result = new HashSet<Long>(); // Using a Set to filter out doubles
        for (JsonNode node : jsonNodes) {
            if (node.has(propertyName)) {
                Long propertyValue = node.get(propertyName).asLong();
                if (propertyValue > 0) { // Just to be safe
                    result.add(propertyValue);
                }
            }
        }
        return result;
    }

    public static Set<String> gatherStringPropertyFromJsonNodes(Iterable<JsonNode> jsonNodes, String propertyName) {
        Set<String> result = new HashSet<String>(); // Using a Set to filter out doubles
        for (JsonNode node : jsonNodes) {
            if (node.has(propertyName)) {
                String propertyValue = node.get(propertyName).asText();
                if (propertyValue != null) { // Just to be safe
                    result.add(propertyValue);
                }
            }
        }
        return result;
    }

    public static List<JsonNode> filterOutJsonNodes(List<JsonLookupResult> lookupResults) {
        List<JsonNode> jsonNodes = new ArrayList<JsonNode>(lookupResults.size());
        for (JsonLookupResult lookupResult : lookupResults) {
            jsonNodes.add(lookupResult.getJsonNode());
        }
        return jsonNodes;
    }

    // Helper classes

    public static class JsonLookupResult {

        private String id;
        private String name;
        private JsonNode jsonNode;

        public JsonLookupResult(String id, String name, JsonNode jsonNode) {
            this(name, jsonNode);
            this.id = id;
        }

        public JsonLookupResult(String name, JsonNode jsonNode) {
            this.name = name;
            this.jsonNode = jsonNode;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public JsonNode getJsonNode() {
            return jsonNode;
        }

        public void setJsonNode(JsonNode jsonNode) {
            this.jsonNode = jsonNode;
        }

    }

}
